package com.hb.alerts;

import com.hb.domain.Spittle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsOperations;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.support.JmsUtils;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.Session;

public class AlertServiceImpl implements AlertService {
    private JmsOperations jmsOperations;

    @Autowired
    public AlertServiceImpl(JmsOperations jmsOperations) {
        this.jmsOperations = jmsOperations;
    }

    @Override
    public void sendSpittleAlert(final Spittle spittle) {
        // 发送消息。JmsOperations 的 send() 方法的第一个参数是 JMS 目的地名称，标识消息将发送给谁。当调用 send() 方法时，JmsTemplate 将负责获得 JMS 连接、会话并代表发送者发送消息。
        // JmsTemplate 代表发送者来负责处理发送消息的复杂过程。
        // 就是这么简单！注意，sendSpittleAlert() 方法专注于组装和发送消息。在这里没有连接或会话管理的代码，JmsTemplate 帮我们处理了所有的相关事项，而且我们也不需要捕获 JMSException 异
        // 常。JmsTemplate 将捕获抛出的所有 JMSException 异常，然后重新抛出相对应的 Spring 某一种非检查型异常。
        jmsOperations.send(
                "spittle.alert.queue",  // 指定目的地
                // 我们使用 MessageCreator（在这里的实现是作为一个匿名内部类）来构造消息。在 MessageCreator 的 createMessage() 方法中，我们通过 session 创建了一个对象消息：
                // 传入一个 Spittle 对象，返回一个对象消息。
                new MessageCreator() {
                    @Override
                    public Message createMessage(Session session) throws JMSException {
                        // 创建消息
                        return session.createObjectMessage(spittle);
                    }
                });

        // 如果在配置中配置 JmsTemplate 时制定了默认的目的地的话，那么调用 JmsTemplate 的 send() 方法时，我们可以去除第一个参数（目的地）了。这种形式的 send() 方法只需要传入一个 MessageCreator。
        // 因为希望消息发送给默认目的地，所以我们没有必要再指定特定的目的地。
        jmsOperations.send(new MessageCreator() {
            @Override
            public Message createMessage(Session session) throws JMSException {
                // 创建消息
                return session.createObjectMessage(spittle);
            }
        });

        // 但是如果我们使用消息转换器的话，发送消息会更加简单。除了 send() 方法，JmsTemplate 还提供了 convertAndSend() 方法。与 send() 方法不同，convertAndSend() 方法并不需要 MessageCreator 作为参数。这是因为 convertAndSend() 会使用内置
        // 的消息转换器（message converter）为我们创建消息。就像变魔术一样，Spittle 会在发送之前转换为 Message。不过就像所有的魔术一样，JmsTemplate 内部会进行一些处理。它使用一个 MessageConverter 的实现类将对象转换为 Message。
        jmsOperations.convertAndSend(spittle);
    }

    /**
     * 用 JmsTemplate 接收消息
     *
     * @return
     */
    public Spittle receiveSpittleAlert() {
        try {
            // 因为我们知道 Spittle 消息是作为一个对象消息来发送的，所以它可以在到达后转型为 ObjectMessage。然后，我们调用 getObject() 方法把 ObjectMessage 转换为 Spittle 对象并返回此对象。
            ObjectMessage receivedMessage = (ObjectMessage) jmsOperations.receive();
            return (Spittle) receivedMessage.getObject();
        } catch (JMSException jmsException) {
            // 但是这里存在一个问题，我们不得不对可能抛出的 JMSException 进行处理。正如我已经提到的，JmsTemplate 可以很好地处理抛出的 JmsException 检查型异常，然后把异常转换为 Spring 非检查型异
            // 常 JmsException 并重新抛出。但是它只对调用 JmsTemplate 的方法时才适用。JmsTemplate 无法处理调用 ObjectMessage 的 getObject() 方法时所抛出的 JMSException 异常。因此，我们要
            // 么捕获 JMSException 异常，要么声明本方法抛出 JMSException 异常。为了遵循 Spring 规避检查型异常的设计理念，我们不建议本方法抛出 JMSException 异常，所以我们选择捕获该异常。在 catch
            // 代码块中，我们使用 Spring 中 JmsUtils 的 convertJmsAccessException() 方法把检查型异常 JMSException 转换为非检查型异常 JmsException。这其实是在其他场景中由 JmsTemplate 为我们做的事情。
            throw JmsUtils.convertJmsAccessException(jmsException);
        }
    }

    /**
     * 在 receiveSpittleAlert() 方法中，我们可以改善的一点就是使用消息转换器。在 convertAndSend() 中，我们已经看到了如何将对象转换为 Message。不过，它们还可以用在接收端，也就是
     * 使用 JmsTemplate 的 receiveAndConvert()
     * @return
     */
    public Spittle receiveSpittleAlert1() {
        // 现在，没有必要将 Message 转换为 ObjectMessage，也没有必要通过调用 getObject() 来获取 Spittle，更无需担心检查型的 JMSException 异常。这个新的 retrieve SpittleAlert() 简洁了许多。
        // 但是，依然还有一个很小且不容易察觉的问题。使用 JmsTemplate 接收消息的最大缺点在于 receive() 和 receiveAndConvert() 方法都是同步的。这意味着接收者必须耐心等待消息的到来，因此这些方法会一直
        // 被阻塞，直到有可用消息（或者直到超时）。同步接收异步发送的消息，是不是感觉很怪异？这就是消息驱动 POJO 的用武之处。4
        return (Spittle) jmsOperations.receiveAndConvert();
    }
}
